﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using yiCommerce.Core;
using yiCommerce.Core.Data;
using yiCommerce.Core.Domain.Addresses;
using yiCommerce.Core.Domain.Customers;
using yiCommerce.Core.Domain.Orders;
using yiCommerce.Core.Domain.Payments;
using yiCommerce.Core.Domain.Shippings;
using yiCommerce.Core.Domain.ShoppingCarts;
using yiCommerce.Core.Domain.Shops;
using yiCommerce.Core.Domain.Tradings;
using yiCommerce.Service.Customers;
using yiCommerce.Service.Payments;
using yiCommerce.Service.Products;
using yiCommerce.Service.Shippings;
using yiCommerce.Service.ShoppingCarts;
using yiCommerce.Service.Tradings;

namespace yiCommerce.Service.Orders
{
    public class OrderProcessingService:IOrderProcessingService
    {
        private IRepository<Shipment> _shipmentRep;
        private ICustomerService _customerService;
        private IShoppingCartService _shoppingCartService;
        private IPaymentService _paymentService;
        private IProductService _productService;
        private ITradingService _tradingService;
        private IOrderService _orderService;
        private IProductVariantAttributeFormatter _productVariantAttributeFormatter;
        private IShippingService _shippingService;
        private IOrderTotalCalculationService _orderTotalCalculationService;

        public OrderProcessingService(IRepository<Shipment> shipmentRep,
            IShoppingCartService shoppingCartService, ICustomerService customerService, IPaymentService paymentService, IProductService productService, ITradingService tradingService, IOrderService orderService, IProductVariantAttributeFormatter productVariantAttributeFormatter, IShippingService shippingService, IOrderTotalCalculationService orderTotalCalculationService)
        {
            _shipmentRep = shipmentRep;
            _shoppingCartService = shoppingCartService;
            _customerService = customerService;
            _paymentService = paymentService;
            _productService = productService;
            _tradingService = tradingService;
            _orderService = orderService;
            _productVariantAttributeFormatter = productVariantAttributeFormatter;
            _shippingService = shippingService;
            _orderTotalCalculationService = orderTotalCalculationService;
        }

        public PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest)
        {

            //think about moving functionality of processing recurring orders (after the initial order was placed) to ProcessNextRecurringPayment() method
            if (processPaymentRequest == null)
                throw new ArgumentNullException("processPaymentRequest");

            if (processPaymentRequest.OrderGuid == Guid.Empty)
                processPaymentRequest.OrderGuid = Guid.NewGuid();

            var result = new PlaceOrderResult();
            try
            {

                //customer
                var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId);
                if (customer == null)
                    throw new ArgumentException("Customer is not set");


                //check whether customer is guest
                if (customer.IsGuest() )
                    throw new Exception("Anonymous checkout is not allowed");


                //load and validate customer shopping cart
                IList<ShoppingCartItem> cart = null;

                cart = customer.ShoppingCartItems.ToList();
                if (cart.Count == 0)
                    throw new Exception("Cart is empty");

                //validate the entire shopping cart
                var warnings = _shoppingCartService.GetShoppingCartWarnings(cart);
                if (warnings.Count > 0)
                {
                    var warningsSb = new StringBuilder();
                    foreach (string warning in warnings)
                    {
                        warningsSb.Append(warning);
                        warningsSb.Append(";");
                    }
                    throw new Exception(warningsSb.ToString());
                }

                //validate individual cart items
                foreach (var sci in cart)
                {
                    var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(
                        sci.ProductVariant, sci.AttributesXml,
                        sci.Quantity);
                    if (sciWarnings.Count > 0)
                    {
                        var warningsSb = new StringBuilder();
                        foreach (string warning in sciWarnings)
                        {
                            warningsSb.Append(warning);
                            warningsSb.Append(";");
                        }
                        throw new Exception(warningsSb.ToString());
                    }
                }

                //sub total (excl tax)


                decimal subTotalWithoutDiscountBase2 = decimal.Zero;
                _orderTotalCalculationService.GetShoppingCartSubTotal(cart,  out subTotalWithoutDiscountBase2);
                //orderSubTotalExclTax = subTotalWithoutDiscountBase2;
    
                //shipping
                if (customer.ShippingAddress == null)
                    throw new Exception("Shipping address is not provided");

                if (!CommonHelper.IsValidEmail(customer.ShippingAddress.Email))
                    throw new Exception("Email is not valid");


                Address shippingAddress = null;
                shippingAddress = (Address)customer.ShippingAddress.Clone();

                //shipping total
                decimal? orderShippingTotalExclTax = null;
               
              orderShippingTotalExclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart);



                //order total

              var orderTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart);
              if (!orderTotal.HasValue)
                  throw new Exception("Order total couldn't be calculated");



              processPaymentRequest.OrderTotal = orderTotal.Value;


              //standard cart
              var processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest);

                if (processPaymentResult.Success)
                {
                    var shopItems = GroupItemsByShop(cart);

                    var trading = _tradingService.GenerateNewTrading();

                    trading.PaymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName;

                    _tradingService.Update(trading);


                    foreach (var si in shopItems)
                    {
                        var shop = si.Key;
                        var shippingStatus = ShippingStatus.NotYetShipped;

                        var order = new Order()
                            {
                                ShopId = shop.Id,
                                OrderGuid = processPaymentRequest.OrderGuid,
                                CustomerId = customer.Id,
                                
                                //OrderSubtotalExclTax = orderSubTotalExclTax,

                                OrderShippingPrice = orderShippingTotalExclTax.Value,
                                OrderTotal = orderTotal.Value,
                                RefundedAmount = decimal.Zero,
                                OrderStatus = OrderStatus.Processing,

                                PaymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName,

                                PaymentStatus = processPaymentResult.NewPaymentStatus,
                                PaidDateUtc = null,
                                ShippingAddress = shippingAddress,
                                ShippingStatus = shippingStatus,

                                CreatedOnUtc = DateTime.Now
                            };


                        _orderService.InsertOrder(order);

                        trading.Orders.Add(new TradingOrder(){TradingId = trading.Id, OrderId = order.Id});
                        _tradingService.Update(trading);

                        result.PlacedTrading = trading;

                        //move shopping cart items to order product variants

                        #region pv

                        foreach (var sc in si)
                        {
                            //prices
                            decimal scUnitPrice = sc.ProductVariant.Price;
                            decimal scSubTotal = sc.ProductVariant.Price*sc.Quantity;

                            //attributes
                            string attributeDescription =
                                _productVariantAttributeFormatter.ToFormattedString(sc.AttributesXml);


                            var itemWeight = _shippingService.GetShoppingCartItemWeight(sc);

                            //save order item
                            var opv = new OrderProductVariant()
                                {
                                    OrderProductVariantGuid = Guid.NewGuid(),
                                    Order = order,
                                    ProductVariantId = sc.ProductVariantId,
                                    AttributeDescription = attributeDescription,
                                    AttributesXml = sc.AttributesXml,
                                    Quantity = sc.Quantity,
                                    ItemWeight = itemWeight,
                                    TotalPrice = scSubTotal,
                                    UnitPrice = scUnitPrice,
                                };
                            order.OrderProductVariants.Add(opv);
                            _orderService.UpdateOrder(order);


                            //inventory
                            _productService.AdjustInventory(sc.ProductVariant, true, sc.Quantity,
                                                            sc.AttributesXml);


                        }

                        #endregion

                    }
                    //clear shopping cart
                    cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci));
                }
            }
            catch (Exception e)
            {
                result.AddError(e.Message);
            }
            return result;
        }

        private List<IGrouping<Shop, ShoppingCartItem>> GroupItemsByShop(IList<ShoppingCartItem> cart)
        {

            var query = from c in cart

                        group c by c.ProductVariant.Product.Shop

                            into g

                            select g;

            var groupedItems = query.ToList();
            foreach (var g in groupedItems)
            {
                foreach (var shoppingCartItem in g)
                {
                    shoppingCartItem.ProductVariant =
                        _productService.GetProductVariantById(shoppingCartItem.ProductVariantId);
                }
            }
            return groupedItems;

        }
        public void DeleteOrder(Order order)
        {
            throw new System.NotImplementedException();
        }

        public void Ship(Shipment shipment, bool notifyCustomer)
        {
            throw new System.NotImplementedException();
        }

        public void Deliver(Shipment shipment, bool notifyCustomer)
        {
            throw new System.NotImplementedException();
        }

        public void CancelOrder(Order order, bool notifyCustomer)
        {
            throw new System.NotImplementedException();
        }

        public void MarkAsAuthorized(Order order)
        {
            throw new System.NotImplementedException();
        }

        public void MarkOrderAsPaid(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            order.PaymentStatus= PaymentStatus.Paid;
            _orderService.UpdateOrder(order);
        }

        public void MarkOrderShipped(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            order.ShippingStatus= ShippingStatus.Shipped;
            _orderService.UpdateOrder(order);
        }

        public void MarkOrderDelivered(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            order.ShippingStatus= ShippingStatus.Delivered;
            _orderService.UpdateOrder(order);
            
        }

        public void MarkOrderAsCompleted(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            order.OrderStatus = OrderStatus.Complete;
            _orderService.UpdateOrder(order);

        }

        public IList<string> Refund(Order order)
        {
            throw new System.NotImplementedException();
        }
    }
}